/*
 * Copyright (c) 2022 PATEO CONNECT+ (Nanjing) Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "voice_assistant_event_target.h"
#include "securec.h"
#include "voice_assistant_napi_tools.h"
#include <unistd.h>
#include <uv.h>

namespace OHOS {
namespace CarVoiceAssistant {
    struct EventTargetCB {
        napi_env env_;
        sptr<VoiceAssistantEventTarget> eventTarget_;
        VoiceAssistantEventType type_;
        BaseEvent* event_;
    };

    RecognizeStateEvent::RecognizeStateEvent(bool isRecognizing)
        : isRecognizing_(isRecognizing)
    {
    }

    napi_value RecognizeStateEvent::ToJsObject(napi_env env)
    {
        napi_value object = nullptr;
        napi_create_object(env, &object);

        napi_value isRecognizingToJs = nullptr;
        napi_get_boolean(env, isRecognizing_, &isRecognizingToJs);
        napi_set_named_property(env, object, "isRecognizing", isRecognizingToJs);

        return object;
    }

    ArsResultEvent::ArsResultEvent(std::string text)
        : text_(text)
    {
    }

    napi_value ArsResultEvent::ToJsObject(napi_env env)
    {
        napi_value object = nullptr;
        napi_create_object(env, &object);

        napi_value textToJs = nullptr;
        napi_create_string_utf8(env, text_.c_str(), text_.length(), &textToJs);
        napi_set_named_property(env, object, "result", textToJs);

        return object;
    }

    TTSPlayStateEvent::TTSPlayStateEvent(bool isPlaying)
        : isPlaying_(isPlaying)
    {
    }

    napi_value TTSPlayStateEvent::ToJsObject(napi_env env)
    {
        napi_value object = nullptr;
        napi_create_object(env, &object);

        napi_value isPlayingToJs = nullptr;
        napi_get_boolean(env, isPlaying_, &isPlayingToJs);
        napi_set_named_property(env, object, "isPlaying", isPlayingToJs);

        return object;
    }

    VoiceAssistantEventTarget::VoiceAssistantEventTarget(napi_env env) { env_ = env; }

    VoiceAssistantEventTarget::~VoiceAssistantEventTarget() { }

    void VoiceAssistantEventTarget::On(napi_env env, VoiceAssistantEventType type, napi_value callbackRef, napi_value thisVar)
    {
        struct VoiceAssistantEventListener listener;
        listener.env_ = env;
        listener.eventType_ = type;
        listener.isOnce_ = false;
        napi_create_reference(env, callbackRef, 1, &listener.callbackRef_);
        napi_create_reference(env, thisVar, 1, &listener.thisVarRef_);
        eventListenerList_.push_back(listener);
        VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget:On:size:%{public}d", eventListenerList_.size());
    }

    void VoiceAssistantEventTarget::Once(napi_env env, VoiceAssistantEventType type, napi_value callbackRef, napi_value thisVar)
    {
        struct VoiceAssistantEventListener listener;
        listener.env_ = env;
        listener.eventType_ = type;
        listener.isOnce_ = true;
        napi_create_reference(env, callbackRef, 1, &listener.callbackRef_);
        napi_create_reference(env, thisVar, 1, &listener.thisVarRef_);
        eventListenerList_.push_back(listener);
        VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget:Once:size:%{public}d", eventListenerList_.size());
    }

    void VoiceAssistantEventTarget::Off(napi_env env, VoiceAssistantEventType type, napi_value thisVar)
    {
        eventListenerList_.remove_if([env, thisVar, type](VoiceAssistantEventListener listener) -> bool {
            bool isEqualsThisVar = false;
            napi_value thisVarTemp = nullptr;
            napi_get_reference_value(env, listener.thisVarRef_, &thisVarTemp);
            napi_strict_equals(env, thisVar, thisVarTemp, &isEqualsThisVar);
            bool isMatch = (isEqualsThisVar && listener.eventType_ == type);
            if (isMatch) {
                napi_delete_reference(env, listener.thisVarRef_);
                napi_delete_reference(env, listener.callbackRef_);
            }
            return isMatch;
        });
        VOICE_ASSISTANT_LOGI("VoiceAssistantEventTargets:Off:size:%{public}d", eventListenerList_.size());
    }

    void VoiceAssistantEventTarget::EmitOnWakeUp()
    {
        VOICE_ASSISTANT_LOGI("EmitOnWakeUp");
        Emit(VoiceAssistantEventTypeOnWakeUp, nullptr);
    }

    void VoiceAssistantEventTarget::EmitRecognizeStateChanged(bool isRecognizing)
    {
        VOICE_ASSISTANT_LOGI("EmitRecognizeStateChanged:%{public}s", isRecognizing ? "true" : "false");
        RecognizeStateEvent* event = new RecognizeStateEvent(isRecognizing);
        Emit(VoiceAssistantEventTypeRecognizeStateChanged, (BaseEvent*)event);
    }

    void VoiceAssistantEventTarget::EmitAsrResult(std::string text)
    {
        VOICE_ASSISTANT_LOGI("EmitAsrResult:%{public}s", text.c_str());
        ArsResultEvent* event = new ArsResultEvent(text);
        Emit(VoiceAssistantEventTypeAsrResult, (BaseEvent*)event);
    }

    void VoiceAssistantEventTarget::EmitTTSPlayStateChanged(bool isPlaying)
    {
        VOICE_ASSISTANT_LOGI("EmitTTSPlayStateChanged:%{public}s", isPlaying ? "true" : "false");
        TTSPlayStateEvent* event = new TTSPlayStateEvent(isPlaying);
        Emit(VoiceAssistantEventTypeTTSPlayStateChanged, (BaseEvent*)event);
    }

    void VoiceAssistantEventTarget::Emit(VoiceAssistantEventType type, BaseEvent* event)
    {
        VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget::Emit:%{publidc}d", (int)getpid());
        uv_loop_s* loop = nullptr;
        napi_get_uv_event_loop(env_, &loop);
        if (loop == nullptr) {
            VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget::Emit loop == nullptr");
            return;
        }

        uv_work_t* work = new (std::nothrow) uv_work_t;
        if (work == nullptr) {
            VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget::Emit No memory work == nullptr");
            return;
        }

        EventTargetCB* eventTaegetCB = new (std::nothrow) EventTargetCB { .env_ = env_, .eventTarget_ = this, .type_ = type, .event_ = event };

        work->data = (void*)eventTaegetCB;

        int ret = uv_queue_work(
            loop, work, [](uv_work_t* work) {},
            [](uv_work_t* work, int status) {
                VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget::Emit start work");

                // Js Thread
                if (work == nullptr) {
                    VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget::Emit work == nullptr");
                    return;
                }
                EventTargetCB* eventTargetCB = reinterpret_cast<EventTargetCB*>(work->data);
                napi_handle_scope scope = nullptr;
                napi_open_handle_scope(eventTargetCB->env_, &scope);

                sptr<VoiceAssistantEventTarget> eventTarget = eventTargetCB->eventTarget_;
                for (std::list<VoiceAssistantEventListener>::iterator listenerIterator = eventTarget->eventListenerList_.begin(); listenerIterator != eventTarget->eventListenerList_.end(); ++listenerIterator) {
                    if (listenerIterator->eventType_ != eventTargetCB->type_) {
                        continue;
                    }

                    napi_env env = listenerIterator->env_;
                    napi_ref thisVarRef = listenerIterator->thisVarRef_;
                    napi_ref callbackRef = listenerIterator->callbackRef_;

                    napi_value thisVar = nullptr;
                    napi_get_reference_value(env, thisVarRef, &thisVar);

                    napi_value callbackFunc = nullptr;
                    napi_get_reference_value(env, callbackRef, &callbackFunc);

                    napi_value callbackValues[2] = { 0 };
                    callbackValues[0] = GetUndefinedToJS(env);
                    if (eventTargetCB->event_) {
                        callbackValues[1] = eventTargetCB->event_->ToJsObject(env);
                    } else {
                        callbackValues[1] = GetUndefinedToJS(env);
                    }

                    napi_value returnVal = nullptr;
                    napi_call_function(env, thisVar, callbackFunc, 2, callbackValues, &returnVal);
                    if (listenerIterator->isOnce_) {
                        eventTargetCB->eventTarget_->Off(env, listenerIterator->eventType_, thisVar);
                    }
                }

                napi_close_handle_scope(eventTargetCB->env_, scope);

                if (eventTargetCB->event_) {
                    delete eventTargetCB->event_;
                }

                if (eventTargetCB) {
                    delete eventTargetCB;
                }
                delete work;
            });

        if (ret != 0) {
            VOICE_ASSISTANT_LOGI("VoiceAssistantEventTarget::Emit failed to execute libuv work queue");
            delete work;
        }
    }

}
}